package com.googlecode.cswish.struts.hibernate;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.annotation.Resource;

import org.apache.log4j.Logger;

import com.googlecode.cswish.model.ModelInfo;
import com.googlecode.cswish.model.PropertyInfo;
import com.googlecode.cswish.struts.spring.BeanIntrospector;
import com.googlecode.cswish.struts.spring.ObjectFactory;
import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.reflection.ReflectionContextState;


/**
 * Wrap a static model to a dynamic model
 * 
 * @author Jerry.Feng Date: 2008-5-15
 */
@Resource(shareable=false)
public class DynamicModel implements IDynamicModel, IMixedModel {
	
	private static final Logger logger = Logger.getLogger(DynamicModel.class);
	
	public static Method READ_METHOD;
	public static Method WRITE_METHOD;
	static {
		try {
			READ_METHOD = DynamicModel.class.getMethod("get", Object.class);
			WRITE_METHOD = DynamicModel.class.getMethod("put", String.class, Object.class);
		} catch (Exception ex) {
			logger.error("Fail to get the GET/PUT method", ex);
		}
	}
	
	private Map<String, Object> dynamicProperties = new HashMap<String, Object>();
	
	private String entityName;
	
	/**
	 * synchronize the current value to top hibernate model
	 */
	private DynamicModel hibernateOwnerMode;
	
	/**
	 * see InstantiatingNullHandler
	 */
	private Map context;
	
	private ModelInfo modelInfo;
	
	@Resource
	private ObjectFactory objectFactory;
	
	@Resource
	private BeanIntrospector beanIntrospector;
	
	@Inject
	private XWorkConverter xworkConverter;

	public void setObjectFactory(ObjectFactory objectFactory) {
		this.objectFactory = objectFactory;
	}
	
	public void setBeanIntrospector(BeanIntrospector beanIntrospector) {
		this.beanIntrospector = beanIntrospector;
	}

	public void setEntityName(String entityName) {
		this.entityName = entityName;
		dynamicProperties.put(ENTITY_NAME, entityName);
		
		this.modelInfo = beanIntrospector.getModelInfo(entityName);
	}

	public String getEntityName() {
		return entityName;
	}

	public Map<String, Object> getDynamicProperties() {
		return dynamicProperties;
	}

	public void setDynamicProperties(Map<String, Object> dynamicProperties) {
		this.dynamicProperties = dynamicProperties;
	}

	public boolean enableAutoInitNull() {
		if (context != null) {
			return ReflectionContextState.isCreatingNullObjects(context);
		} else {
			return false;
		}
	}
	
	public Object initValue(String sKey) {
		PropertyInfo propertyInfo = modelInfo.getProperty(sKey);
		if (logger.isInfoEnabled()) {
			if (propertyInfo == null) {
				logger.info("Can't get the undefined property:" + sKey + " from model:" + entityName);
			}
		}
		
		Object value = null;
		if (propertyInfo != null && !propertyInfo.isSimpleObj()) {
			Class<?> clazz = propertyInfo.getTypeClass();
			if (clazz != null) {
				if (Collection.class.isAssignableFrom(clazz)) {
		            value = new ArrayList();
		        } else if (clazz == Map.class) {
		            value = new HashMap();
		        }
			}
			
			if (value == null) {
				try {
					value = objectFactory.buildBean(propertyInfo.getType(), context);
					
					// Update the value to its owner model if the dynamic model 
					// is only a temporary container (i.e. it's not mapped to any hibernate model)
					if (value != null && value instanceof DynamicModel) {
						DynamicModel dynamicModel = (DynamicModel) value;
						dynamicModel.setEntityName(propertyInfo.getGenericType());
						dynamicModel.setContext(this.context);
						
						if (!propertyInfo.isTableColumn()) {
							dynamicModel.hibernateOwnerMode = this.hibernateOwnerMode == null
								? this : this.hibernateOwnerMode;
						}	
					}
				} catch (Exception ex) {
					logger.debug("Fail to create instance for type:" +  propertyInfo.getType(), ex);
				}
			}
			dynamicProperties.put(sKey, value);
		}
		return value;
	}
	
	@Override
	public Object get(Object key) {
		String sKey = key == null ? null : key.toString();
		Object value = dynamicProperties.get(sKey);
		
		if (value == null && sKey != null && enableAutoInitNull() ) {
			value = initValue(sKey);
		}
		return value;
	}
	
	@SuppressWarnings("unchecked")
	public <T> T convert(Object value, Class<T> toType) {
		if (value != null && toType.isAssignableFrom(value.getClass())) {
			return (T)value;
		}
		
		try {
			T newValue = (T) xworkConverter.convertValue(null, null, null, null, value, toType);
			return newValue;
		} catch (Exception ex) {
		}
		return null;
	}
	
	@Override
	public Object put(String key, Object value) {
		// call xwork converter
		PropertyInfo propertyInfo = modelInfo.getProperty(key);
		if (propertyInfo == null) {
			if (logger.isInfoEnabled()) {
				logger.info("Can't add the undefined value to property:" + key + " of model:" + entityName);
			}
			return null;
		}
		
		Class<?> targetClass = propertyInfo.getTypeClass();
		Object convertedValue = convert(value, targetClass);

		Object oldValue = dynamicProperties.put(key, convertedValue);
		
		// Update the value to it's owner model at the same time
		if (hibernateOwnerMode != null) {
			hibernateOwnerMode.dynamicProperties.put(key, convertedValue);
		} else {
			// Update its child model if exists
			String containerName = modelInfo.getContainerName(key);
			if (containerName != null) {
				Object container = get(containerName);
				if (container == null) {
					container = initValue(containerName);
				}
				if (container != null && container instanceof Map) {
					((Map)container).put(key, value);
				} else {
					if (logger.isDebugEnabled()) {
						logger.debug("Can't find the Map container: " + containerName);
					}
				}
			}
		}
		return oldValue;
	}

	@Override
	public void putAll(Map m) {
		for (Object key : m.keySet()) {
			if (key instanceof String) {
				put((String)key, m.get(key));
			}
		}
	}

	////////////////////////////Invalid properties/////////////////////////////
	@Deprecated
	@Override
	public void clear() {
		dynamicProperties.clear();
	}

	@Deprecated
	@Override
	public boolean containsKey(Object key) {
		return dynamicProperties.containsKey(key);
	}

	@Deprecated
	@Override
	public boolean containsValue(Object value) {
		return dynamicProperties.containsValue(value);
	}

	@Deprecated
	@Override
	public Set entrySet() {
		return dynamicProperties.entrySet();
	}

	@Deprecated
	@Override
	public boolean isEmpty() {
		return dynamicProperties.isEmpty();
	}

	@Deprecated
	@Override
	public Set keySet() {
		return dynamicProperties.keySet();
	}

	@Deprecated
	@Override
	public Object remove(Object key) {
		return dynamicProperties.remove(key);
	}

	@Deprecated
	@Override
	public int size() {
		return dynamicProperties.size();
	}

	@Deprecated
	@Override
	public Collection<Object> values() {
		return dynamicProperties.values();
	}

	@Override
	public void setContext(Map context) {
		this.context = context;
	}
}